﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.ComponentModel;
using System.Windows.Controls;
using Trinity.Components.Combat;
using Trinity.Components.Combat.Resources;
using Trinity.Framework;
using Trinity.Framework.Avoidance.Structures;
using Trinity.Framework.Actors.ActorTypes;
using Trinity.Framework.Helpers;
using Trinity.Framework.Objects;
using Trinity.Framework.Reference;
using Trinity.UI;
using Trinity.Settings;
using Zeta.Bot;
using Zeta.Common;
using Zeta.Game;
using Zeta.Game.Internals.Actors;
using Zeta.Game.Internals.SNO;


namespace Trinity.Routines.Necromancer
{
    public sealed class NecromancerBoneStorm : NecroMancerBase, IRoutine
    {
        #region Definition

        public string DisplayName => "伊纳瑞斯尸矛骨矛策略 国外论坛搬运";
        public string Description => "点击下面链接可以查看配装和技能.";
        public string Author => "Lokhan";
        public string Version => "0.7";
        public string Url => "https://www.d3planner.com/201640764";
        public Build BuildRequirements => null;
        public float PrimaryEnergyMin = 0.90f;
        public float LandOfTheDeadRange = 35f;
        public int AquilaResourceCost = 10;
        public float BoneArmorRange = 10f;
        public int BoneArmorCost = 10;
        public float BoneStormRange = 12f;
        public int BoneArmorStacks = Legendary.WisdomOfKalan.IsEquipped ? 15 : 10;

        #endregion

        public TrinityPower GetOffensivePower()
        {
            TrinityPower power;
            TrinityActor target;
            Vector3 position;

            if (TryBoneArmor(out power))
                return power;

            if (ShouldBloodRush(out position))
                return BloodRush(position);

            // Land of the dead gets higher priority because of it's use for a survival cooldown
            if (ShouldLandOfTheDead(out target))
                return LandOfTheDead();

            if (ShouldPrioritizeCorpseLance(out target))
                return CorpseLance(target);

            if (TryDecrepify(out power))
                return power;

            // Used for generating more essence or health
            if (ShouldGrimScytheForRecovery(out target))
                return GrimScythe(target);


            if (ShouldCorpseLance(out target))
                return CorpseLance(target);

            if (ShouldGrimScythe(out target))
                return GrimScythe(target);

            return null;
        }

        protected override bool ShouldLandOfTheDead(out TrinityActor target)
        {
            target = null;

            if (!Skills.Necromancer.LandOfTheDead.CanCast())
                return false;

            // Land of The Dead should be used mostly to clear elites, but can also
            // be useful as a survivability cooldown. 
            if ((IsInCombat && TargetUtil.AnyElitesInRange(LandOfTheDeadRange)) || (TargetUtil.ClusterExists(LandOfTheDeadRange, 5) && Player.CurrentHealthPct < Settings.LandOfTheDeadDefensePct)) {

                target = TargetUtil.GetBestClusterUnit() ?? CurrentTarget;
            }

            return target != null;
        }

        protected bool TryDecrepify(out TrinityPower power)
        {
            TrinityActor target;
            power = null;

            if (!Skills.Necromancer.Decrepify.CanCast())
                return false;

            // anti-spam workaround
            if (Skills.Necromancer.Decrepify.TimeSinceUse < 2500) 
                return false;

            // If using Aquila, we should only Decrepify if we are above the minimum resource requirements
            // in order to keep the legendary bonus active.
            // If we're below the minimum, we should try to Grim Scythe first, then use decrepify.
            if (Legendary.AquilaCuirass.IsEquipped && ((Player.PrimaryResource - AquilaResourceCost) / Player.PrimaryResourceMax) < PrimaryEnergyMin) {
                Core.Logger.Log(LogCategory.Routine, $"Player has Aquila, essence is too low for Decrepify. Must use Grim Scythe first.");
                target = TargetUtil.GetBestClusterUnit(15f, 20f) ?? CurrentTarget;
                power = GrimScythe(target);

                return power != null;
            }

            // If Aquila isn't equipped, we should stop using it below 50 essence, just to conserve
            if (!Legendary.AquilaCuirass.IsEquipped && Player.PrimaryResource < 50) {
                target = TargetUtil.GetBestClusterUnit(15f, 20f) ?? CurrentTarget;
                power = GrimScythe(target);

                return power != null;
            }

            target = TargetUtil.BestTargetWithoutDebuff(60f, SNOPower.P6_Necro_Decrepify);
            power = Decrepify(target);

            return power != null;
        }

        protected bool ShouldGrimScytheForRecovery(out TrinityActor target)
        {
            target = null;

            if (!Skills.Necromancer.GrimScythe.CanCast())
                return false;

            // Needs to be used to generate essence when we drop below the Aquila
            // DR threshold
            if (Player.PrimaryResourcePct < PrimaryEnergyMin && Legendary.AquilaCuirass.IsEquipped) {
                target = TargetUtil.GetBestClusterUnit(15f, 20f) ?? CurrentTarget;
                Core.Logger.Log(LogCategory.Routine, $"Player no longer has Aquila DR bonus, generating essence using Grim Scythe");
            }

            // Without Aquila, we just need to make sure we have SOME essence.
            if (Player.PrimaryResource < 50 && !Legendary.AquilaCuirass.IsEquipped) {
                target = TargetUtil.GetBestClusterUnit(15f, 20f) ?? CurrentTarget;
                Core.Logger.Log(LogCategory.Routine, $"Player has {Player.PrimaryResource} Essence, generating using Grim Scythe");
            }

            // If health drops below a certain threshold, we should use it for healing
            if (Player.CurrentHealthPct < Settings.GrimScytheHealPct && TargetUtil.ClusterExists(20f, 4)) {
                Core.Logger.Log(LogCategory.Routine, $"Player at {Player.CurrentHealthPct}, healing using Grim Scythe");
                target = TargetUtil.GetBestClusterUnit(15f, 20f) ?? CurrentTarget;
            }
            
            return target != null;
        }

        protected bool ShouldPrioritizeCorpseLance(out TrinityActor target)
        {
            target = null;

            if (!Skills.Necromancer.CorpseLance.CanCast())
                return false;

            // We should only grab units within range of bone storm
            if (Skills.Necromancer.LandOfTheDead.IsBuffActive) {
                Core.Logger.Log(LogCategory.Routine, $"Land of The Dead is active, prioritizing Corpse Lance");
                target = TargetUtil.BestEliteInRange(BoneStormRange) ?? TargetUtil.GetBestClusterUnit(10f, BoneStormRange);
            }

            return target != null;
        }

        protected bool TryBoneArmor(out TrinityPower power)
        {
            TrinityActor target;
            power = null;

            if (!Skills.Necromancer.BoneArmor.CanCast())
                return false;

            if (!TargetUtil.AnyMobsInRange(BoneArmorRange))
                return false;

            // Since CanCast isn't working properly for this skill, we need to check
            // time since last use. With cooldown reduction/blood is power, 4 seconds
            // is a realistic check. 
            if (Skills.Necromancer.BoneArmor.TimeSinceUse < 4000)
                return false;

            // Bone Armor lasts 60 seconds and should never fall off, but just in case
            if (Skills.Necromancer.BoneArmor.TimeSinceUse > 30000) {
                Core.Logger.Log(LogCategory.Routine, $"Bone Armor about to fall off, casting it on any target");
                power = BoneArmor();
                return power != null;
            }

            // If using Aquila and already below the required essence level, use Grim Scythe instead
            if (Legendary.AquilaCuirass.IsEquipped && Player.PrimaryResourcePct < PrimaryEnergyMin) {
                 Core.Logger.Log(LogCategory.Routine, $"Player has Aquila, essence is too low for Bone Armor. Must use Grim Scythe first.");
                target = TargetUtil.GetBestClusterUnit(15f, 20f) ?? CurrentTarget;
                power = GrimScythe(target);

                return power != null;
            }

            // If we aren't yet at maximum stacks, get there ASAP
            if (Skills.Necromancer.BoneArmor.BuffStacks < BoneArmorStacks) {
                Core.Logger.Log(LogCategory.Routine, $"Bone Armor stacks at {Skills.Necromancer.BoneArmor.BuffStacks}, casting to get {BoneArmorStacks}");
                power = BoneArmor();

                return power != null;
            }

            // We also want to bone storm if we have more than X targets, if we're below % health or on cooldown against elites
            var isBigCluster = TargetUtil.ClusterExists(BoneArmorRange, 6);
            var isEliteInRange = TargetUtil.AnyElitesInRange(BoneArmorRange);
            if (isBigCluster || isEliteInRange || Player.CurrentHealthPct < Settings.BoneArmorHealthPct) {
                Core.Logger.Log(LogCategory.Routine, $"Player in danger, casting Bone Armor for stun.");
                power = BoneArmor();

                return power != null;
            }

            return power != null;
        }

        protected override bool ShouldCorpseLance(out TrinityActor target)
        {
            target = null;
            if (!Skills.Necromancer.CorpseLance.CanCast())
                return false;

            if (TargetUtil.CorpseCount(85f) <= 0)
                return false;

            target = TargetUtil.BestEliteInRange(BoneStormRange) ?? TargetUtil.GetBestClusterUnit(10f, BoneStormRange);
            return target != null;
        }

        protected override bool ShouldBloodRush(out Vector3 position)
        {
            position = Vector3.Zero;

            if (!Skills.Necromancer.BloodRush.CanCast())
                return false;

            if (Skills.Necromancer.BloodRush.CooldownRemaining > 0)
                return false;

            // Don't Blood Rush during Land of The Dead
            if (Skills.Necromancer.LandOfTheDead.IsBuffActive && TargetUtil.ClusterExists(BoneStormRange, 3))
                return false;

            // Find a safespot with no monsters within kite range.
            Core.Avoidance.Avoider.TryGetSafeSpot(out position, 23f, 27f, Player.Position,
                node => !TargetUtil.AnyMobsInRangeOfPosition(node.NavigableCenter, KiteDistance));
         
            // Don't vault into molten core/arcane.
            if (!Core.Avoidance.InCriticalAvoidance(ZetaDia.Me.Position) && Core.Avoidance.Grid.IsIntersectedByFlags(ZetaDia.Me.Position, position, AvoidanceFlags.CriticalAvoidance))
                return false;

            position = TargetUtil.GetBestClusterPoint();
            return position != Vector3.Zero;
        }

        protected override bool ShouldGrimScythe(out TrinityActor target)
        {
            target = null;

            if (!Skills.Necromancer.GrimScythe.CanCast())
                return false;

            target = TargetUtil.GetBestClusterUnit(15f, 20f) ?? CurrentTarget;
            return target != null;
        }

        public TrinityPower GetDefensivePower()
        {
            return GetBuffPower();
        }

        public TrinityPower GetBuffPower()
        {
            if (Player.CurrentHealthPct < 0.25 && Skills.Necromancer.BloodRush.CanCast())
            {
                return BloodRush(Avoider.SafeSpot);
            }
            // Put up bone armor when running around with high cluster size setting and not yet fighting
            if (!Skills.Necromancer.BoneArmor.IsBuffActive && TargetUtil.AnyMobsInRange(15f, 3))
            {
                return BoneArmor();
            }
            return null;
        }

        public TrinityPower GetDestructiblePower()
        {
            return DefaultDestructiblePower();
        }

        public TrinityPower GetMovementPower(Vector3 destination)
        {
            TrinityPower power;

            if (!IsInCombat || CurrentTarget != null && CurrentTarget.IsElite && CurrentTarget.Position.Distance(destination) <= 10f)
            {
                if (TryBloodrushMovement(destination, out power))
                    return power;
            }

            // Do not move if Land of The Dead is active and we have targets within 15 yards
            //P4_ItemPassive_Unique_Ring_057 v2 = stopping for directions, v1 = walking endlessly

            return Walk(destination);
        }

        #region Settings      

        public override int ClusterSize => Settings.ClusterSize;
        public override float EmergencyHealthPct => Settings.EmergencyHealthPct;

        IDynamicSetting IRoutine.RoutineSettings => Settings;
        public NecromancerBoneStormSettings Settings { get; } = new NecromancerBoneStormSettings();

        public sealed class NecromancerBoneStormSettings : NotifyBase, IDynamicSetting
        {
            //private SkillSettings _wrathOfTheBerserker;
            //private SkillSettings _furiousCharge;

            private int _clusterSize;
            private float _emergencyHealthPct;
            private float _landOfTheDeadDefensePct;
            private float _grimScytheHealPct;
            private float _boneArmorHealthPct;

            [DefaultValue(5)]
            public int ClusterSize
            {
                get { return _clusterSize; }
                set { SetField(ref _clusterSize, value); }
            }

            [DefaultValue(0.5f)]
            public float EmergencyHealthPct
            {
                get { return _emergencyHealthPct; }
                set { SetField(ref _emergencyHealthPct, value); }
            }

            [DefaultValue(0.35f)]
            public float LandOfTheDeadDefensePct
            {
                get { return _landOfTheDeadDefensePct; }
                set { SetField(ref _landOfTheDeadDefensePct, value); }
            }

            [DefaultValue(0.6f)]
            public float GrimScytheHealPct
            {
                get { return _grimScytheHealPct; }
                set {SetField(ref _grimScytheHealPct, value); }
            }

            [DefaultValue(0.85f)]
            public float BoneArmorHealthPct
            {
                get { return _boneArmorHealthPct; }
                set {SetField(ref _boneArmorHealthPct, value); }
            }

            public override void LoadDefaults()
            {
                base.LoadDefaults();
                //WrathOfTheBerserker = WrathOfTheBerserkerDefaults.Clone();
                //FuriousCharge = FuriousChargeDefaults.Clone();
            }

            #region IDynamicSetting

            public string GetName() => GetType().Name;
            public UserControl GetControl() => UILoader.LoadXamlByFileName<UserControl>(GetName() + ".xaml");
            public object GetDataContext() => this;
            public string GetCode() => JsonSerializer.Serialize(this);
            public void ApplyCode(string code) => JsonSerializer.Deserialize(code, this, true);
            public void Reset() => LoadDefaults();
            public void Save() { }

            #endregion
        }

        #endregion
    }
}


